package com.cmos.um.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;


/**
 * 【类】简单的数据库操作
 */
@Service("Ssql")
@Transactional
public class simpleSQL
{
    /**
     * 【工具】 日志
     */
    private final static Logger logger = LoggerFactory.getLogger(simpleSQL.class);

    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//

    /**
     * 【工具】 根据MAP数据源和指定的字段——如果在数据源中有此字段的数据的话——组织SQL语句中的赋值或等式子句
     * @param  prefix           子句的前缀
     * @param  connector        赋值式之间的连接符
     * @param  ignore_empty_str 忽略空字符串
     * @param  params           数据源MAP集合
     * @param  columns          要组装的字段名数组（以+号开头的表示值是数字，带=表示原样导入而不再生成赋值等式）
     * @return String           组装好的Where子句（或空字符串）
     */
    private String equal_clause(String prefix, String connector, boolean ignore_empty_str, Map<String,Object> params, String[] columns)
    {
        Object x;
        String s,v;

        //1 检查输入参数
        if(params==null || params.size()<=0 || columns.length<=0)
            return "";
        if(prefix==null)
            prefix="";
        if(connector==null)
            connector="";

        //2 组装条件部分
        StringBuffer sb=new StringBuffer();
        for(String k:columns)
        {
            //a 跳过无效的字段名
            if(k==null || k.length()<=0)
                continue;

            //b 带=表示原样导入（不再生成赋值等式）
            if(k.contains("="))
            {
                if(sb.length()>0) { sb.append(connector); sb.append(" "); }
                sb.append(k);
            }

            //c 按字段名在数据源Map中找值并加入条件
            else
            {
                String  quotes_start = "'"  ; // 用于括起值的开始符号（缺省为引号）
                String  quotes_end   = "'"  ; // 用于括起值的结束符号（缺省为引号）

                //i 处理以+标识为整数的字段
                if(k.startsWith("+"))
                {
                    k=k.substring(1);
                    quotes_start=quotes_end="";
                }

                //ii 取值(无值的忽略)——原值
                if((x=params.get(k))!=null && !((s=x.toString()).isEmpty() && ignore_empty_str) && !(v=quotes_start+db(s)+quotes_end).isEmpty())
                {
                    if(sb.length()>0)
                    sb.append(connector+" ");
                    sb.append(k+"="+v +" ");
                }

                //iii 取值(无值的忽略)——起始值
                if((x=params.get(k+"_FROM"))!=null && !((s=x.toString()).isEmpty() && ignore_empty_str) && !(v=quotes_start+db(s)+quotes_end).isEmpty())
                {
                    if(sb.length()>0)
                    sb.append(connector+" ");
                    sb.append(k+">="+v +" ");
                }

                //iv 取值(无值的忽略)——终止值
                if((x=params.get(k+"_TO"))!=null && !((s=x.toString()).isEmpty() && ignore_empty_str) && !(v=quotes_start+db(s)+quotes_end).isEmpty())
                {
                    if(sb.length()>0)
                    sb.append(connector+" ");
                    sb.append(k+"<="+v +" ");
                }
            }
        }
        if(sb.length()<=0)
            return "";

        //3 组装完整子句并返回，函数正常结束
        return (prefix+" "+sb.toString());
    }



    /**
     * 【方法】 根据MAP数据源和指定的字段——如果在数据源中有此字段的数据的话——组织SQL语句中的Where子句
     * @param  params  数据源MAP集合
     * @param  columns 要组装的字段名（以+号开头的表示值是数字，带=表示原样导入而不再生成赋值等式，带*表示对值进行单双引号的入库转换）
     * @return String  组装好的Where子句（或空字符串）
     */
    public String where(Map<String,Object> params, String... columns) throws Exception
    {
        //1 检查输入参数
        if(params==null)
            throw new Exception("不能生成SQL语句中的where子句，程序存在错误——调用where()函数时没有给出 Map<String,Object> params 数据源参数");
        if(columns.length<=0)
            throw new Exception("不能生成SQL语句中的where子句，程序存在错误——调用where()函数时没有给出任何的 String... columns 字段");

        //2 准确定位
        if(params.get("params")!=null)
            params=(Map<String,Object>)params.get("params");

        //3 调用生成等式的函数生成where子句
        String where_clause=equal_clause("where", "and", true, params, columns);
        if(where_clause.isEmpty())
            throw new Exception("不能生成SQL语句中的where子句，可能是没有给出查询的任何条件(字段)");

        //4 函数正常结束
        return where_clause;
    }



    /**
     * 【方法】 根据MAP数据源和指定的字段——如果在数据源中有此字段的数据的话——组织SQL语句中的Set子句
     * @param  params  数据源MAP集合
     * @param  columns 要组装的字段名（以+号开头的表示值是数字，带=表示原样导入而不再生成赋值等式，带*表示对值进行单双引号的入库转换）
     * @return String  组装好的Where子句（或空字符串）
     */
    public String set(Map<String,Object> params, String... columns) throws Exception
    {
        //1 检查输入参数
        if(params==null)
            throw new Exception("不能生成SQL语句中的set子句，程序存在错误——调用set()函数时没有给出 Map<String,Object> params 数据源参数");
        if(columns.length<=0)
            throw new Exception("不能生成SQL语句中的set子句，程序存在错误——调用set()函数时没有给出任何的 String... columns 字段");

        //2 准确定位
        if(params.get("params")!=null)
            params=(Map<String,Object>)params.get("params");

        //3 调用生成等式的函数生成where子句
        String set_clause=equal_clause("set", ",", false, params, columns);
        if(set_clause.isEmpty())
            throw new Exception("不能生成SQL语句中的set子句，可能是没有给出查询的任何条件(字段)");

        //4 函数正常结束
        return set_clause;
    }



    /**
     * 【方法】 根据MAP中是否存在start和limit来生成SQL语句中的Limit子句——用于Select语句
     * @param  params 数据源MAP集合
     * @return String 组装好的Limit子句（或空字符串）
     */
    public String limit(Map<String,Object> params)
    {
        Object x     = params.get("start"); if(x==null                      ) return "";
        String start = x.toString(       ); if(!start.matches("[0-9][0-9]*")) return "";
               x     = params.get("limit"); if(x==null                      ) return "";
        String limit = x.toString(       ); if(!limit.matches("[0-9][0-9]*")) return "";

        return " LIMIT "+start+","+limit;
    }



    /**
     * 【方法】 根据MAP中是否存在limit来生成SQL语句中的Limit子句——只判断limit而不判断start——用于Update语句
     * @param  params 数据源MAP集合
     * @return String 组装好的Limit子句（或空字符串）
     */
    public String limitOnly(Map<String,Object> params)
    {
        Object x     = params.get("limit"); if(x==null                      ) return "";
        String limit = x.toString(       ); if(!limit.matches("[0-9][0-9]*")) return "";

        return " LIMIT "+limit;
    }

    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//



    //// 工具：向数据库写字段时，进行单引号的转换
    ///  参数：字段值（必须为字符串型）
    //   返回：转换后的字段值
    public String db(String value)
    {
        String s;
        if(value==null) s= "";
        else            s= value.replace("\\u0027","'").replace("\\u003d","=") // 编码替换
                                .replace("\\","\\\\")                          // 转义符替换
                                .replace("'","\\'");                           // 单引号替换（双引号是不需要转义的）
        return s;
    }



    //// 工具：从数据库读字段时，进行单双引号的（内部）转换
    ///  参数：字段值（必须为字符串型）
    //   返回：转换后的字段值
    public String doubleQuoteRestore(String value)
    {
        String s;
        if(value==null) s= "";
        else            s= value.replace("\\\"", "`");
        return s;
    }



    /**
     * 【方法】 CC数据库表中的中文转变为正常的字符串
     * @param  str    转换前的字符串
     * @return String 转换后的字符串
     */
    public String fromUnicode(String str)
    {
        String[] strArr = str.split("&#x");
        StringBuffer reStrBuf = new StringBuffer();
        int a = 0;
        try
        {
            for (int i = 0; i < strArr.length; i++)
            {
                if (strArr[i].indexOf(";") == -1)
                {
                    reStrBuf.append(strArr[i]);
                    continue;
                }
                if (strArr[i].length() != 5)
                {
                    String[] strArrSub = strArr[i].split(";");

                    if(strArrSub != null)
                    {
                        int subStrLen =strArrSub[0].length();
                        strArrSub[0] = strArrSub[0].substring(0,subStrLen<4?subStrLen:4); // Modified by wangshuda 2011/11/02

                        a = Integer.parseInt(strArrSub[0], 16);
                        char strChar = (char) a;
                        reStrBuf.append(String.valueOf(strChar));
                        if(strArrSub.length > 1)
                        {   // be sure then length is 2.
                            reStrBuf.append(strArrSub[1]);
                        }
                    }
                }
                else
                {
                    strArr[i] = strArr[i].substring(0, 4);
                    a = Integer.parseInt(strArr[i], 16);
                    char strChar = (char) a;
                    reStrBuf.append(String.valueOf(strChar));
                }
            }
        }
        catch(Exception ex)
        {
            return "转换中文格式出了问题（"+ex.getMessage()+"）";
        }

        return reStrBuf.toString();
    }



    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//



    /**
     * 【方法】 将（字符）输入流中的数据读取到一个字符串中去
     * @param  in_stream  （字符）输入流
     * @return String     从流中读取的字符串
     * @throws Exception  程序出错时会抛出错误
     */
    public String stream2string(Reader in_stream) throws Exception
    {
        int    i      = -1;
        String s      = null;
        char[] buffer = new char[4096];

        //1 检查输入参数
        if(in_stream==null)
            throw new Exception("在调用stream2string(in_stream)函数时没有给出“输入流in_stream”参数");

        //2 将输入流中的数据组合起来
        StringWriter out = new StringWriter();
        try
        {
            while((i=in_stream.read(buffer))!=-1)
            out.write(buffer,0,i);
            out.flush();

            s=out.toString();
        }
        finally
        {
            try { in_stream.close(); } catch (Exception ex) { }
            try {       out.close(); } catch (Exception ex) { }
        }

        //3 函数正常结束，返回组织好的字符串
        return s;
    }

    /**
     * 【方法】 从Map<String,Object>中提取字符串值
     * @param   m       Map<String,Object>
     * @param   key     键
     * @return  String 提取出的字符串形式的值
     */
    public static String m2s(Map<String,Object> m,String key)
    {
                             if(m==null || m.isEmpty()) return null;
        Object X=m.get(key); if(X==null               ) return null;
        return X.toString();
    }

    /**
     * 【方法】 从Map<String,Object>中提取整数值
     * @param   m       Map<String,Object>
     * @param   key     键
     * @return  Integer 提取出的整数形式的值的对象
     */
    public static Integer m2i(Map<String,Object> m,String key)
    {
                               if(m==null || m.isEmpty()) return null;
        Object X=m.get(key);   if(X==null               ) return null;
        String s=X.toString(); if(!s.matches("[0-9]+")  ) return null;
        return Integer.valueOf(s);
    }

    /**
     * 【方法】 浮点数转换为（显式用的）字符串
     * @param   d      浮点数
     * @return  String 转换后的字符串
     */
    public String d2s(double d)
    {
        return String.format("%f",d).replaceAll("\\.0*$","");
    }



    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//



    /**
     * 【方法】 向指定URL发出HTTP请求（POST或GET），并得到字符串回应
     * @param   method       是"POST"还是"GET"请求
     * @param   url          HTTP服务URL地址
     * @param   content_type 内容类型
     * @param   body         POST消息体的内容（对于GET消息无效）
     * @param   head_map     消息头的额外设置，没有时可为null
     * @return  String       从响应消息中取得的字符串
     */
    public String httpRequest(String method, String address, String content_type, String body, Map<String,String> head_map) throws Exception
    {
        //1 检查输入参数
        if(method==null || !method.matches("(POST|GET)"))                           throw new Exception("调用httpRequest(url,body,content_type,method,head_map)函数时给出的参数不正确，没有给出“HTTP请求方式 method”参数或此参数的内容格式不对——目前给出的为“"+method+"”");
        if(address==null || !address.startsWith("http://"))                         throw new Exception("调用httpRequest(url,body,content_type,method,head_map)函数时给出的参数不正确，没有给出“HTTP请求地址 url”参数或此参数不是有效的HTTP地址——目前给出的为“"+address+"”");
        if(method.equals("POST") && (content_type==null || content_type.isEmpty())) throw new Exception("调用httpRequest(url,body,content_type,method,head_map)函数时给出的参数不正确，对于POST请求没有给出“消息体内容的类型 content_type”参数");
        if(body==null) body="";

        //2 利用httpClient插件发出消息并获得回应
        String result ="";
        String step   ="";
        try
        {
                                                               HttpClient httpClient = new HttpClient();
                                                               HttpMethod httpMethod = null;

                                                               if(method.equalsIgnoreCase("POST"))
                                                               {
            step="生成HTTP POST消息实体";                          httpMethod = new PostMethod(address);
            step="将消息体内容和内容类型标识放入POST消息实体";     ((PostMethod)httpMethod).setRequestEntity(new StringRequestEntity(body, content_type, "UTF-8"));
                                                               }
                                                               else
                                                               {
            step="生成HTTP GET消息实体";                           httpMethod = new GetMethod(address);
                                                               }

                                                               if(head_map!=null && !head_map.isEmpty())
                                                               {
                                                                   for(Entry<String, String> entry:head_map.entrySet())
                                                                   {
            step="将附加的HTTP头参数加入HTTP消息实体";                 httpMethod.addRequestHeader(entry.getKey(),entry.getValue());
                                                                   }
                                                               }

            step="发出HTTP请求并获得回应";                     int code = httpClient.executeMethod(httpMethod);
                                                               if(code<200 || code>299)
                                                                   throw new Exception("HTTP "+method+"请求返回了错误码（"+code+"）");

            step="获得HTTP响应体的输入流";                     InputStream responseIn = httpMethod.getResponseBodyAsStream();
            step="将HTTP响应数据从“流”转变为“字符串”";     result= stream2string(new InputStreamReader(responseIn, "UTF-8"));
        }
        catch (Exception e)
        {
            throw new Exception("（用httpClient插件）向“"+address+"”发送HTTP "+method+"消息（"+step+"）时出现问题，"+e.getMessage());
        }

        //3 函数正常结束，返回获得的字符串
         return result;
    }

    /**
     * 【方法】 发出HTTP Post消息（消息体中附带Json字符串），并得到回应消息体中的字符串（也是Json字符串）
     * @param  address   目的地址URL
     * @param  body      消息体中的字符串
     * @return String    返回的消息体中的字符串
     * @throws Exception 发送出现问题时会抛出异常
     */
    public String httpPost_Json(String address, String body) throws Exception
    {
        return httpRequest("POST",address,"application/json",body,null);
    }

    /**
     * 【方法】 发出HTTP Get消息，并得到回应消息体中的字符串
     * @param  address   目的地址URL
     * @return String    返回的消息体中的字符串
     * @throws Exception 发送出现问题时会抛出异常
     */
    public String httpGet(String address) throws Exception
    {
        String s =null;
        int    i =0;

        //1 检查输入参数
        if(address==null || !address.startsWith("http://"))
            throw new Exception("在调用simpleSQL::httpPost_String(address,body,content_type)函数发出HTTP消息时，输入参数address为空或不是有效的Http地址");

        //2 发出HTTP Get消息并得到回应
        String            result_string =null; // 返回的消息体（字符串）

        HttpURLConnection urlConn   =null; // HTTP 连接
        BufferedReader    in_stream =null; // 请求消息中的消息体对应的输入流
        String            step      ="";   // 当前正在进行的操作
        try
        {
            //a 准备消息头
            step="检查URL地址";             URL url = new URL(address);
            step="打开HTTP连接";            urlConn = (HttpURLConnection) url.openConnection();
            step="设置HTTP请求消息的属性";  urlConn.setDoOutput(false);
                                            urlConn.setDoInput (true);
                                            // urlConn.setUseCaches(false);
                                            // urlConn.setFixedLengthStreamingMode(body.getBytes().length);
                                            // urlConn.setRequestProperty("connection", "close"); // disables Keep Alive
                                            urlConn.setRequestMethod("GET");

            //b 发送Get消息
            step="发出请求";                urlConn.connect();

            //c 获得消息结果
            step="获得结果码";              int    return_code   = urlConn.getResponseCode();    // return_code为HTTP回应码
            step="获得结果描述";            String return_string = urlConn.getResponseMessage(); // return_string为服务端返回的字符串

            //d 检查结果
            step="检查结果码";              if(return_code<200 || return_code>299) throw new Exception("对方返回了错误码 "+return_code+" "+return_string+"。");

            //e 获得消息体内容
            step="创建接收响应的缓冲期";    StringBuffer sb = new StringBuffer();
            step="打开“接收流”";          in_stream= new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
            step="读取“接收流”";          while((s=in_stream.readLine())!=null) sb.append(s);
            step="整理“接收流”为字符串";  result_string = sb.toString();
            step="检查收到的响应消息(体)";  if(result_string.isEmpty()) throw new Exception("对方返回了空字符串");
        }
        catch(Exception e)
        {
            throw new Exception("向“"+address+"”发送HTTP Get消息（"+step+"）时出现问题，"+e.getMessage());
        }
        finally
        {
            if(in_stream!=null) in_stream.close();
            if(urlConn  !=null) urlConn.disconnect();
        }

        //3 调试
        /*DEBUG*/ logger.debug("HTTP Get （"+address+"）： 》》》》"+result_string+"匚");

        //4 函数正常结束，返回得到的消息体内的字符串
        return result_string;
    }

    /**
     * 【方法】 发出HTTP Post消息（消息体中附带字符串），并得到回应消息体中的字符串（也是字符串）
     * @param  address      目的地址URL
     * @param  body         消息体中的字符串
     * @param  content_type 内容的类型
     * @return String       返回的消息体中的字符串
     * @throws Exception    发送出现问题时会抛出异常
     */
    public String httpPost_String(String address, String body, String content_type) throws Exception
    {
        String s =null;
        int    i =0;

        //1 检查输入参数
        if(address==null || !address.startsWith("http://"))
            throw new Exception("在调用simpleSQL::httpPost_String(address,body,content_type)函数发出HTTP消息时，输入参数address为空或不是有效的Http地址");
        if(content_type==null || content_type.isEmpty())
            throw new Exception("在调用simpleSQL::httpPost_String(address,bodycontent_type)函数发出HTTP消息时，输入参数content_type为空");
        if(body==null)
            body="";

        //2 发出HTTP Post消息（附带Json数据）并得到回应
        String            result_string =null; // 返回的消息体（字符串）

        HttpURLConnection urlConn    =null; // HTTP 连接
        BufferedReader    in_stream  =null; // 请求消息中的消息体对应的输入流
        OutputStream      out_stream =null; // 响应消息中的消息体对应的输出流
        String            step       ="";   // 当前正在进行的操作
        try
        {
            //a 准备消息头
            step="检查URL地址";             URL url = new URL(address);
            step="打开HTTP连接";            urlConn = (HttpURLConnection) url.openConnection();
            step="设置HTTP请求消息的属性";  urlConn.setDoOutput(true);
                                            urlConn.setDoInput (true);
                                            // urlConn.setUseCaches(false);
                                            // urlConn.setFixedLengthStreamingMode(body.getBytes().length);
                                            urlConn.setRequestProperty("connection", "close"); // disables Keep Alive
                                            urlConn.setRequestProperty("Content-type",content_type); // 在此设置消息体内容的格式
                                            urlConn.setRequestMethod("POST");

            //b 发送（消息头和）消息体
            step="打开“发送流”";          out_stream = urlConn.getOutputStream();
            step="将消息送入“发送流”";    out_stream.write(body.getBytes());
            step="刷新/发出消息体";         out_stream.flush();

            //c 获得消息结果
            step="获得结果码";              int    return_code   = urlConn.getResponseCode();    // return_code为HTTP回应码
            step="获得结果描述";            String return_string = urlConn.getResponseMessage(); // return_string为服务端返回的字符串

            //d 检查结果
            step="检查结果码";              if(return_code<200 || return_code>299) throw new Exception("对方返回了错误码"+return_code+" "+return_string+"。");

            //e 获得消息体内容
            step="创建接收响应的缓冲期";    StringBuffer sb = new StringBuffer();
            step="打开“接收流”";          in_stream= new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
            step="读取“接收流”";          while((s=in_stream.readLine())!=null) sb.append(s);
            step="整理“接收流”为字符串";  result_string = sb.toString();
            step="检查收到的响应消息(体)";  if(result_string.isEmpty()) throw new Exception("对方返回了空字符串");
        }
        catch(Exception e)
        {
            throw new Exception("向“"+address+"”发送HTTP Post消息（"+step+"）时出现问题，"+e.getMessage()+"，原计划要发出的消息为“"+body+"”");
        }
        finally
        {
            if( in_stream!=null)  in_stream.close();
            if(out_stream!=null) out_stream.close();
            if(urlConn   !=null) urlConn.disconnect();
        }

        //3 调试
        /*DEBUG*/ logger.debug("HTTP Post （"+address+"）："+body+" 》》》》"+result_string+"匚");

        //4 函数正常结束，返回得到的消息体内的字符串
        return result_string;
    }

    /**
     * 【方法】 发出HTTP Put消息，消息体中附带文件——用于向apache上传文件
     * @param  address   目的地址URL
     * @param  file_name 文件名
     * @throws Exception 发送出现问题时会抛出异常
     */
    public void httpPut_File(String address,String file_name) throws Exception
    {
        int i =0;

        HttpURLConnection    urlConn           =null; // HTTP 连接
        BufferedOutputStream out_stream        =null; // 响应消息中的消息体对应的输入流
        FileInputStream      file_input_stream =null; // 要读取的文件对应的输入流

        //1 检查输入参数
        if(address==null || !address.startsWith("http://"))
            throw new Exception("在调用simpleSQL::httpPut_File(address,file_name)函数发出HTTP消息时，输入参数服务器address为空或不是有效的Http地址");
        if(file_name==null || file_name.isEmpty())
            throw new Exception("在调用simpleSQL::httpPut_File(address,file_name)函数发出HTTP消息时，输入参数文件名file_name为空");

        String [] ss   = file_name.split("/");
        String    name = ss[ss.length-1];

        //2 发出HTTP Post消息（附带Json数据）并得到回应
        String result_string =null; // 返回的消息体（字符串）
        String step          =""  ; // 当前正在进行的操作
        try
        {
            //a 准备消息头
            step="检查URL地址";             URL url = new URL(address);
            step="打开HTTP连接";            urlConn = (HttpURLConnection) url.openConnection();
            step="设置HTTP请求消息的属性";  urlConn.setDoOutput(true);
                                            urlConn.setDoInput (true);
                                            urlConn.setRequestMethod("PUT");

            //b 从文件中读取数据填入消息体
            step="打开HTTP通道发送流";             out_stream  = new BufferedOutputStream(urlConn.getOutputStream());
            step="打开文件";                file_input_stream  = new FileInputStream(new File(file_name));
            step="准备发送数据缓冲区";      byte [] bytes      = new byte[1024];
            step="读取文件";                while((i=file_input_stream.read(bytes,0,1024))>0) {
            step="写入HTTP通道";                out_stream.write(bytes,0,i);                  }
            step="刷新/发出消息体";         out_stream.flush();

            //c 获得消息结果并检查
            step="获得结果码";              int    return_code   = urlConn.getResponseCode();    // return_code为HTTP回应码
            step="获得结果描述";            String return_string = urlConn.getResponseMessage(); // return_string为服务端返回的字符串
            step="检查结果码";              if(return_code<200 || return_code>299) throw new Exception("对方返回了错误码 "+return_code+" "+return_string+"。");
        }
        catch(Exception e)
        {
            throw new Exception("在利用HTTP Put消息向"+address+"上传文件"+file_name+"时（"+step+"）出现问题，"+e.getMessage());
        }
        finally
        {
            if(       out_stream!=null)        out_stream.close();
            if(file_input_stream!=null) file_input_stream.close();
            if(urlConn          !=null) urlConn.disconnect();
        }

        //3 调试
        /*DEBUG*/ logger.debug("HTTP Put （"+address+"）：文件名="+file_name+" 》》》》"+result_string+"匚");

        // 备注 ：以下为利用Commons-HttpClient/3.1工具向apache上传文件的写法
        // PutMethod http_put_method=new PutMethod(address);
        // http_put_method.setRequestEntity(new InputStreamRequestEntity(new FileInputStream(new File(file_name))));
        // int return_code=(new HttpClient()).executeMethod(http_put_method);

        //4 函数正常结束
        return;
    }



    /**
     * 【方法】 利用HTTP（Get）下载文件
     * @param  address       目的地址URL
     * @param  new_file_name 下载后保存的新文件名，为空则采用原文件名
     * @throws Exception     发送出现问题时会抛出异常
     */
    public void httpGet_File(String address,String new_file_name) throws Exception
    {
        int i =0;

        HttpURLConnection    urlConn            =null; // HTTP 连接
        BufferedInputStream  in_stream          =null; // 响应消息中的消息体对应的输入流
        FileOutputStream     file_output_stream =null; // 要写入的文件对应的输出流

        //1 检查输入参数
        if(address==null || !address.startsWith("http://"))
            throw new Exception("在调用simpleSQL::httpGet_File(address,new_file_name)函数发出HTTP消息时，输入参数服务器address为空或不是有效的Http地址");
        if(new_file_name==null || new_file_name.isEmpty())
        {
            String [] ss=address.split("/");
            new_file_name=ss[ss.length-1];
        }

        //2 发出HTTP Post消息（附带Json数据）并得到回应
        String result_string =null; // 返回的消息体（字符串）
        String step          =""  ; // 当前正在进行的操作
        try
        {
            //a 准备和发出Get消息
            step="检查URL地址";             URL url = new URL(address);
            step="打开HTTP连接";            urlConn = (HttpURLConnection)url.openConnection();
            step="设置HTTP请求消息的属性";  urlConn.setDoOutput(true);
                                            urlConn.setDoInput (true);
                                            urlConn.setRequestMethod("GET");

            step="发出Get消息";             urlConn.connect();

            step="获得结果码";              int    return_code   = urlConn.getResponseCode();    // return_code为HTTP回应码
            step="获得结果描述";            String return_string = urlConn.getResponseMessage(); // return_string为服务端返回的字符串
            step="检查结果码";              if(return_code<200 || return_code>299) throw new Exception("对方返回了错误码 "+return_code+" "+return_string+"。");

            //b 从返回消息中读取获得文件
            step="打开文件";                file_output_stream = new FileOutputStream(new_file_name);
            step="打开HTTP通道接收流";               in_stream = new BufferedInputStream(urlConn.getInputStream());
            step="准备接收数据缓冲区";           byte [] bytes = new byte[1024];
            step="读取HTTP响应数据";        while((i=in_stream.read(bytes,0,1024))>0) {
            step="写入文件";                file_output_stream.write(bytes,0,i);      }
            step="刷新文件";                file_output_stream.flush();

            //c 获得消息结果并检查
        }
        catch(Exception e)
        {
            throw new Exception("在利用HTTP Get消息下载网络上的文件“"+address+"”时（"+step+"）出现问题，"+e.getMessage());
        }
        finally
        {
            if(         in_stream!=null)          in_stream.close();
            if(file_output_stream!=null) file_output_stream.close();
            if(           urlConn!=null)       urlConn.disconnect();
        }

        //3 调试
        /*DEBUG*/ logger.debug("HTTP Get （"+address+"）：文件名="+new_file_name+" 》》》》"+result_string+"匚");

        //4 函数正常结束，返回得到的消息体内的字符串
        return;
    }

    /**
     * 【工具】保存通过HTTP Post消息上传的文件
     * @param     request       Post请求消息的数据镀锡i昂
     * @param     path          保存目录
     * @param     new_file_name 新的文件名，为null时表示用原文件名
     * @return    String        原文件名
     * @exception Exception     出错时抛出异常
     */
    public String saveFileFromPostRequest(HttpServletRequest request,String path, String new_file_name) throws Exception
    {
        //1 检查输入参数
        if(request==null)                                 throw new Exception("程序错误：在调用simpleSQL::saveFileFromPostRequest()函数时，没有给出“请求实体request”参数");
        if(path==null || !path.matches("[/0-9a-zA-Z_]+")) throw new Exception("程序错误：在调用simpleSQL::saveFileFromPostRequest()函数时，没有给出“保存路径path”参数");

        //2 解析MIME格式（多部分）消息体
        MultipartHttpServletRequest multi_request =null;
        {
            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
            if(!multipartResolver.isMultipart(request))
                throw new Exception("请求消息不是多部分的，所以其中并没有携带文件");

            multi_request = (MultipartHttpServletRequest)request;
        }

        //3 获取头一个文件，将其拷贝到服务器下
        String old_file_name =null;

        Iterator<String> iter = multi_request.getFileNames(); // 取得request中的所有文件名
        while(iter.hasNext())
        {
            //a 取得文件、旧文件名和新的完整文件名（含路径）
            MultipartFile file = multi_request.getFile(iter.next());
            if(file==null)
                continue;

            old_file_name = file.getOriginalFilename();
            if(old_file_name==null || old_file_name.isEmpty())
            {
                if(new_file_name==null || new_file_name.isEmpty())
                    throw new Exception("从请求消息中和调用函数的参数中均不能获得文件名");
                else
                    old_file_name =null;
            }
            else
            {
                if(new_file_name==null || new_file_name.isEmpty())
                    new_file_name = old_file_name;
                else
                    new_file_name = new_file_name.replace("${OLD_FILE_NAME}",old_file_name);
            }

            String ful_file_name = (path+"/"+new_file_name).replace("//","/");

            //b 创建目录和文件，将文件内容从请求消息转移到实际(新)文件中
            try
            {
                File localFolder = new File(path);
                if(!localFolder.exists())
                    localFolder.mkdirs();

                File localFilePath = new File(ful_file_name);
                file.transferTo(localFilePath);
            }
            catch(Exception e)
            {
                throw new Exception("将HTTP-Post请求消息中的文件("+old_file_name+")保存到服务器文件("+ful_file_name+")中时出现错误，"+e.getMessage());
            }

            //c 只取一个文件，其它的忽略
            break;
        }

        //4 函数正常结束，返回原文件名
        return old_file_name;
    }


    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//



    /**
     * 【方法】 整理原始请求中的输入参数
     * @param  params    输入(2层)参数
     * @throws Exception
     */
    public void putinOrder(Map<String,Object> params) throws Exception
    {
        Object X =null;
        String s =null;

        //1 获得主要参数
        Object P          =null; // 主要参数集
        String service_id =null; // 业务号
        String vcc_id     =null; // 企业号
        String start      =null; // 起始行号
        String limit      =null; // 最大行数
        {
            P = params.get("params");
            if(P==null)
                throw new Exception("没有带“params”参数");

            service_id=(String)params.get("busicode");
            if(service_id==null || !service_id.matches("[0-9]{12}"))
                throw new Exception("没有带“业务号busicode”参数，或参数格式不正确（目前为"+service_id+"）");

            vcc_id=service_id.substring(0,6);

            X=params.get("start");
            if(X!=null)
            {
                start=X.toString();
                if(!start.matches("[0-9]+"))
                    throw new Exception("“（页）开始行号start”参数格式不正确（目前为"+start+"）");
            }

            X=params.get("limit");
            if(X!=null)
            {
                limit=X.toString();
                if(!limit.matches("[0-9]+"))
                    throw new Exception("“（页）最大行数limit”参数格式不正确（目前为"+limit+"）");
            }
        }

        //2 参数形式转化
        Map<String,Object> M = null;
        try
        {
            M= DataUtil.json2Map(P);
            if(M==null)
                throw new Exception("转换为空null");
        }
        catch(Exception e)
        {
            throw new Exception("不符合 JSON 格式，"+e.getMessage()+"（收到："+P.toString()+"）");
        }

        //3 转化后的参数放回
        params.put("params", M); // 将字符串形式的params参数转化为Map

        //4 保持里外的兼容性
        X=M.get("service_id"); if(X==null) M.put("service_id",service_id);
        X=M.get(    "vcc_id"); if(X==null) M.put(    "vcc_id",    vcc_id);
        X=M.get(     "start"); if(X==null) M.put(     "start",     start);
        X=M.get(     "limit"); if(X==null) M.put(     "limit",     limit);

        //5 函数正常结束
        return;
    }



    //=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//=//



    /**
     * 【方法】 获得中文格式的当前时间字符串
     * @return  String 当前的时间字符串，格式为“YYYY年MM月DD日hh时mm分ss秒”
     */
    public String zhNowTime()
    {
        Calendar cr=Calendar.getInstance();
        return String.format("%04d年%02d月%02d日%02d时%02d分%02d秒",cr.get(Calendar.YEAR       ),cr.get(Calendar.MONTH)+1,cr.get(Calendar.DAY_OF_MONTH),
                                                                    cr.get(Calendar.HOUR_OF_DAY),cr.get(Calendar.MINUTE) ,cr.get(Calendar.SECOND      ));
    }

    /**
     * 【方法】 获得新方内部格式的当前时间字符串
     * @return  String 当前的时间字符串，格式为“YYYYMMDDhhmmss”
     */
    public static String cinNowTime()
    {
        Calendar cr=Calendar.getInstance();
        return String.format("%04d%02d%02d%02d%02d%02d",cr.get(Calendar.YEAR       ),cr.get(Calendar.MONTH)+1,cr.get(Calendar.DAY_OF_MONTH),
                                                        cr.get(Calendar.HOUR_OF_DAY),cr.get(Calendar.MINUTE) ,cr.get(Calendar.SECOND      ));
    }

    /**
     * 【方法】 获得新方内部格式的当前月字符串
     * @return  String 当前的时间字符串，格式为“YYYYMM”
     */
    public String cinNowMonth()
    {
        Calendar cr=Calendar.getInstance();
        return String.format("%04d%02d",cr.get(Calendar.YEAR),cr.get(Calendar.MONTH)+1);
    }


    /**
     * 【方法】 获得新方内部格式的指定时间偏移后的时间字符串
     * @param   field  要偏移的时间字段：Calendar.YEAR，Calendar.MONTH，Calendar.DAY_OF_MONTH，Calendar.HOUR_OF_DAY，Calendar.MINUTE，Calendar.SECOND
     * @param   delta  偏移值
     * @return  String 偏移后的时间字符串，格式为“YYYYMMDDhhmmss”
     */
    public String cinAfterTime(int field, int delta)
    {
        Calendar cr=Calendar.getInstance();
        cr.add(field, delta);
        return String.format("%04d%02d%02d%02d%02d%02d",cr.get(Calendar.YEAR       ),cr.get(Calendar.MONTH)+1,cr.get(Calendar.DAY_OF_MONTH),
                                                        cr.get(Calendar.HOUR_OF_DAY),cr.get(Calendar.MINUTE) ,cr.get(Calendar.SECOND      ));
    }
}
